// SUGARSCAPE MODEL FOR SWARM
// Based on Nelson Minar's 1996-1997 model; modified by 
// Andre Manoel (andremanoel@gmail.com) with research  
// purposes. See file README for more information.

#include <misc.h> // strdup
#import "ModelSwarm.h"
#import <random.h>  //uniformUnsRand uniformIntRand
#import <analysis.h>

@implementation ModelSwarm

// Code to create the model swarm parameter set
// Note that we don't make probes for all the variables - some variables,
//   like world size, aren't likely to be modified
+ createBegin: aZone
{
  ModelSwarm *obj;
  id <ProbeMap> probeMap;

  obj = [super createBegin: aZone];

  // Parameters for the simulation
  obj->numAgents = 400;
  obj->sugarDatafile = strdup("sugarspace.pgm");
  obj->sugarGrowRate = 1;
  obj->winterDuration = 1;
  obj->winterGrowRate = 1;
  obj->maxSugarMetabolism = 5;
  obj->minInitialSugar = 5;
  obj->maxInitialSugar = 25;
  obj->maxSugar = 10000000;
  obj->maxVision = 6;
  obj->maxForesight = 0;
  obj->deathAgeMin = 99998;
  obj->deathAgeMax = 100000;
  obj->replacement = 0;
  obj->pubertyAgeMin = 0;
  obj->pubertyAgeMax = 0;
  obj->menopauseAgeMin = 0;
  obj->menopauseAgeMax = 0;
  obj->andropauseAgeMin = 0;
  obj->andropauseAgeMax = 0;
  obj->inheritanceOn = 0;
  obj->transmitCulture = 0;
  obj->permitCombat = 0;
  obj->prizeForWinning = 0;
  obj->spiceDatafile = strdup("none");
  obj->spiceGrowRate = 0;
  obj->maxSpiceMetabolism = 0;
  obj->minInitialSpice = 0;
  obj->maxInitialSpice = 0;
  obj->maxSpice = 10000000;
  obj->allowTrading = 0;
  obj->diseaseOn = 0;
  obj->maxMetabLoss = 0;
  obj->numDiseases = 0;
  obj->calcDistEach = 0;
  obj->plagueEach = 99999;
  obj->plagueFor = 0;
  obj->colorsFor = strdup("none");
  obj->worldXSize = 50;
  obj->worldYSize = 50;
  obj->leftXLim = 1;
  obj->rightXLim = 50;
  obj->upYLim = 1;
  obj->downYLim = 50;
  obj->pollutionS = 0;
  obj->pollutionM = 0;
  obj->turnsToDiffuse = 0;
  obj->startPolAt = 0;
  obj->endPolAt = 99999;
  obj->startDiffAt = 0;

  // The creation of a probe map - these parameters are the ones that
  //   are easy to modify in the GUI
  probeMap = [EmptyProbeMap createBegin: aZone];
  [probeMap setProbedClass: [self class]];
  probeMap = [probeMap createEnd];

  [probeMap addProbe: [probeLibrary getProbeForVariable: "numAgents"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "sugarDatafile"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "sugarGrowRate"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "winterGrowRate"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "winterDuration"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "maxSugarMetabolism"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "minInitialSugar"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "maxInitialSugar"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "maxSugar"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "maxVision"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "maxForesight"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "deathAgeMin"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "deathAgeMax"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "replacement"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "pubertyAgeMin"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "pubertyAgeMax"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "menopauseAgeMin"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "menopauseAgeMax"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "andropauseAgeMin"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "andropauseAgeMax"
				    inClass: [self class]]];		
  [probeMap addProbe: [probeLibrary getProbeForVariable: "inheritanceOn"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "transmitCulture"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "permitCombat"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "prizeForWinning"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "spiceDatafile"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "spiceGrowRate"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "maxSpiceMetabolism"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "minInitialSpice"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "maxInitialSpice"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "maxSpice"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "allowTrading"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "diseaseOn"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "maxMetabLoss"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "numDiseases"
				    inClass: [self class]]];
  //[probeMap addProbe: [probeLibrary getProbeForVariable: "calcDistEach"
  //				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "plagueEach"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "plagueFor"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "colorsFor"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "leftXLim"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "rightXLim"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "upYLim"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "downYLim"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "pollutionS"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "pollutionM"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "turnsToDiffuse"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "startPolAt"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "endPolAt"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForVariable: "startDiffAt"
				    inClass: [self class]]];
  [probeMap addProbe: [probeLibrary getProbeForMessage: "addNewRandomAgent"
				    inClass: [self class]]];

  [probeLibrary setProbeMap: probeMap For: [self class]];
  
  return obj;
}

// Build objects is the code where the model swarm creates all the
//   agents and other objects in the simulation model.
- buildObjects
{
  int i;

  [super buildObjects];

  // First, set up the object that is the sugarspace - the environment
  sugarSpace = [SugarSpace createBegin: self];
  [sugarSpace setSizeX: worldXSize Y: worldYSize];
  [sugarSpace setMaxSugarDataFile: sugarDatafile];
  [sugarSpace setMaxSpiceDataFile: spiceDatafile];
  sugarSpace = [sugarSpace createEnd];
  [sugarSpace setSugarGrowRate: sugarGrowRate];
  [sugarSpace setSeasonsOf: winterDuration WithGrowRate: winterGrowRate];
  [sugarSpace setSpiceGrowRate: spiceGrowRate];
  [sugarSpace setPollutionS: pollutionS M: pollutionM diffEach: turnsToDiffuse];
  [sugarSpace setPollutStart: startPolAt pollutEnd: endPolAt
                   diffStart: startDiffAt];
  
  [sugarSpace setPlagueEach: plagueEach forLongAs: plagueFor];

  [self genDiseaseList];

  // Create a list to store all the agents
  agentList = [List create: self];

  // And create a bunch of agents to live in the world
  for (i = 0; i < numAgents; i++)
    [self addNewRandomAgent];
  
  // Create a "reaper queue" to manage agent deaths
  reaperQueue = [List create: self];

  // And create a shuffler object to randomize agent order
  shuffler = [ListShuffler createBegin: self];
  [shuffler setUniformRandom: uniformUnsRand];
  shuffler = [shuffler createEnd];
  
  return self;
}

// Now it's time to create the schedule
- buildActions
{

  id modelActions;  // holds together a sequence of actions for each timestep
  [super buildActions];

  // One time tick, a set of several actions:
  //   randomize the order of agent updates (to be fair)
  //   update all the agents
  //   kill off the agents who just died
  //   update the sugar on the world
  //   update the spice on the world
  //   update the time  
  modelActions = [ActionGroup create: self];
  [modelActions createActionTo: shuffler message: M(shuffleWholeList:) : agentList];
  [modelActions createActionForEach: agentList message: M(step)];
  [modelActions createActionTo: self message: M(reapAgents)];
  [modelActions createActionTo: sugarSpace message: M(updateSugar)];
  [modelActions createActionTo: sugarSpace message: M(updateSpice)];
  [modelActions createActionTo: sugarSpace message: M(updateValues)];
  [modelActions createActionTo: sugarSpace message: M(diffusePollution)];
  [modelActions createActionTo: sugarSpace message: M(updateTime)];
  //[modelActions createActionTo: self message: M(calculateDistance)];

  // The schedule is just running our actions over and over again
  modelSchedule = [Schedule createBegin: self];
  [modelSchedule setRepeatInterval: 1];
  modelSchedule = [modelSchedule createEnd];
  [modelSchedule at: 0 createAction: modelActions];

  return self;
}

// Create a new agent at random and put it in the world
- addNewRandomAgent
{
  int x, y, i, j;
  SugarAgent *agent;

  // Turn off these warnings
  [[sugarSpace getAgentGrid] setOverwriteWarnings: 0];

  // Create the agent object
  agent = [SugarAgent createBegin: self];
  [agent setModelSwarm: self];
  agent = [agent createEnd];

  // Give the agent a random initial position and parameters
  x = [uniformIntRand getIntegerWithMin: leftXLim-1 withMax: rightXLim-1];
  y = [uniformIntRand getIntegerWithMin: upYLim-1 withMax: downYLim-1];
  [sugarSpace addAgent: agent atX: x Y: y];
  [agent setInitialSugar: 
	   [uniformDblRand getDoubleWithMin: minInitialSugar
			   withMax: maxInitialSugar]];
  [agent setInitialSpice: 
	   [uniformDblRand getDoubleWithMin: minInitialSpice
			   withMax: maxInitialSpice]];
  [agent setSugarMetabolism:
	   [uniformDblRand getDoubleWithMin: 1.0 withMax: maxSugarMetabolism]];
  if (strcmp(spiceDatafile, "none") == 0) 
    [agent setSpiceMetabolism: 0];
  else
  [agent setSpiceMetabolism:
           [uniformDblRand getDoubleWithMin: 1.0 withMax: maxSpiceMetabolism]];
  [agent setMaxSugar: maxSugar MaxSpice: maxSpice];
  
  [agent setVision: 
	   [uniformIntRand getIntegerWithMin: 1 withMax: maxVision]];
  [agent setForesight:
           [uniformIntRand getIntegerWithMin: 0 withMax: maxForesight]];
  [agent setDeathAge:
	   [uniformIntRand getIntegerWithMin: deathAgeMin
			   withMax: deathAgeMax]];
			 		   
  [agent setSex: // agent sex-related parameters
  	   [uniformIntRand getIntegerWithMin: 0 withMax: 1]];
  [agent setPubertyAge:
	   [uniformIntRand getIntegerWithMin: pubertyAgeMin
			   withMax: pubertyAgeMax]];
  if ([agent getSex] == 0)
    [agent setInfertilityAge:
	     [uniformIntRand getIntegerWithMin: menopauseAgeMin
		  	     withMax: menopauseAgeMax]];
  else
    [agent setInfertilityAge:
	     [uniformIntRand getIntegerWithMin: andropauseAgeMin
		  	     withMax: andropauseAgeMax]];
		  	     
  if (inheritanceOn == 1) [agent createChildList];

  if (transmitCulture == 1) // agent culture-related parameters
    [agent setTransmitCulture: 1];
  else [agent setTransmitCulture: 0];
  
  for (i = 0; i < 11; i ++) {
    [agent setTagStringAt: i withValue: 
             [uniformIntRand getIntegerWithMin: 0
                             withMax: 1]];
  }
  
  if (permitCombat == 1) // agent combat-related parameters
    [agent setPermitCombat: 1 withPrize: prizeForWinning];
  else [agent setPermitCombat: 0 withPrize: 0];

  if (allowTrading == 1) // agent trade-related parameters
    [agent setAllowTrading: 1];

  if (diseaseOn == 1) { // agent disease-related parameters
    [agent setDiseaseOn: 1 withAsMuchAs: numDiseases];

    // Create random bit string representing the immune system
    for (i = 0; i < 50; i ++) {
      [agent setImmuneStringAt: i withValue: 
               [uniformIntRand getIntegerWithMin: 0
                               withMax: 1]];
    }

    // Copy masterDiseaseList into diseaseList
    for (i = 0; i < numDiseases; i++) {
      for (j = 0; j < 15; j++) {
        [agent setDiseaseListLin: i Col: j withValue: 
                 masterDiseaseList[i][j]];
      }
    }

    // Make active 4 random diseases
    int rndIndexes[4], alreadyUsed;
    for (i = 0; i < 4; i++) {
      do {
        rndIndexes[i] = [uniformIntRand getIntegerWithMin: 0 withMax: numDiseases-1];
        
        alreadyUsed = 0;
        for (j = 0; j < i; j++) 
          if (rndIndexes[i] == rndIndexes[j]) alreadyUsed = 1;
      }
      while (alreadyUsed == 1);

      [agent setDiseaseListLin: rndIndexes[i] Col: 0 withValue: 1];
    } 

  }

  [self agentBirth: agent];

  // Turn the warnings back on
  [[sugarSpace getAgentGrid] setOverwriteWarnings: 1];
  
  return self;
}

// Create a new agent with certain characteristics
- addNewAgentAtX: (int)x Y: (int)y wSugarEndow: (float)e1 wSpiceEndow: (float)e2
             wVision: (int)v wForesight: (int)f wSugarMetabol: (float)m1 
             wSpiceMetabol: (float) m2 wTag: (int *)t;
{
  SugarAgent *agent;

  // Turn off these warnings
  [[sugarSpace getAgentGrid] setOverwriteWarnings: 0];

  // Create the agent object
  agent = [SugarAgent createBegin: self];
  [agent setModelSwarm: self];
  agent = [agent createEnd];

  // Give the agent the given/random initial position and parameters
  [sugarSpace addAgent: agent atX: x Y: y];
  [agent setInitialSugar: e1];
  [agent setInitialSpice: e2];
  [agent setSugarMetabolism: m1];
  [agent setSpiceMetabolism: m2]; 
  [agent setMaxSugar: maxSugar MaxSpice: maxSpice];
  [agent setVision: v];
  [agent setForesight: f];
  [agent setDeathAge:
	   [uniformIntRand getIntegerWithMin: deathAgeMin
			   withMax: deathAgeMax]];
			   
  [agent setSex: // agent sex-related parameters
  	   [uniformIntRand getIntegerWithMin: 0 withMax: 1]];
  [agent setPubertyAge:
	   [uniformIntRand getIntegerWithMin: pubertyAgeMin
			   withMax: pubertyAgeMax]];	   
  if ([agent getSex] == 0)
    [agent setInfertilityAge:
	     [uniformIntRand getIntegerWithMin: menopauseAgeMin
		  	     withMax: menopauseAgeMax]];
  else
    [agent setInfertilityAge:
	     [uniformIntRand getIntegerWithMin: andropauseAgeMin
		  	     withMax: andropauseAgeMax]];

  if (inheritanceOn == 1) [agent createChildList];

  if (transmitCulture == 1) // agent culture-related parameters
    [agent setTransmitCulture: 1];
  else [agent setTransmitCulture: 0];

  int i;
  for (i = 0; i < 11; i++) [agent setTagStringAt: i withValue: t[i]];

  if (permitCombat == 1) // agent combat-related parameters
    [agent setPermitCombat: 1 withPrize: prizeForWinning];
  else [agent setPermitCombat: 0 withPrize: 0];

  if (allowTrading == 1) // agent trade-related parameters
    [agent setAllowTrading: 1];

  [self agentBirth: agent];

// Turn the warnings back on
  [[sugarSpace getAgentGrid] setOverwriteWarnings: 1];
  return self;
}

// Methods to handle the birth and death of agents
- agentBirth: (SugarAgent *)agent
{
  [agentList addLast: agent];
  return self;
}

- agentDeath: (SugarAgent *)agent
{
  [reaperQueue addLast: agent];
  if (replacement)				  // Replacement rule R (p. 32)
    [self addNewRandomAgent];
  return self;
}

// Remove all the agents on the reaperQueue from the agentList;
//   this allows us to defer the death of an agent until it's safe to
//   remove it from the list
- reapAgents
{
  id index, agent;
  
  index = [reaperQueue begin: self];
  while ((agent = [index next]))
    {
      [agentList remove: agent];
      // Dropping the agent leads to unexpected behaviour on combat, so
      //   we won't do it by now
      //[agent drop];
    }
  [reaperQueue removeAll];
  return self;
}

// A technical detail of scheduling
- (id <Activity>)activateIn: swarmContext
{
  [super activateIn: swarmContext];
  [modelSchedule activateIn: self];
  return [self getActivity];
}

// Accessor methods
- (SugarSpace *)getSugarSpace
{
  return sugarSpace;
}

- getAgentList
{
  return agentList;
}

- (char *)getColorsFor
{
  return colorsFor;
}

- (float)getAvgPrice
{
  float sumPrices = 0, noTrades = 0;
  id agent;
  id index = [agentList begin: self];
  while ((agent = [index next]))
    {
      sumPrices += [agent getSumPrices];
      noTrades += [agent getNoTrades];
    }

  return (sumPrices/noTrades);
}

- (float)getAvgPriceLogStdDev;
{
  float logPrice, sumLogPrices = 0, sumSqLogPrices = 0, noTrades = 0;
  float avgLog, avgSqLog;
  id agent;
  id index = [agentList begin: self];
  while ((agent = [index next]))
    {
      logPrice = log([agent getProdPrices]);
      sumLogPrices += logPrice;
      sumSqLogPrices += logPrice*logPrice;
      noTrades += [agent getNoTrades];
    }

  avgLog = sumLogPrices/noTrades;
  avgSqLog = sumSqLogPrices/noTrades;
  return (sqrt(avgSqLog - avgLog*avgLog));
}

- calculateDistance
{
  id agent;
  id index = [agentList begin: self];
  int time, iter;
  float dist;
  
  FILE *output;
  const char* format = "output_t%d_it%d.dat";
  char filename[100];

  time = [sugarSpace getTime];
  if (time == calcDistEach)
    while ((agent = [index next])) [agent resetDistance];

  else if (time > calcDistEach && time % calcDistEach == 0) {
    iter = time/calcDistEach;
    sprintf(filename, format, calcDistEach, iter);
    output = fopen(filename, "a");

    while ((agent = [index next])) {
      dist = [agent getDistance];
      [agent resetDistance];
      fprintf (output, "%f\n", dist);
    }
  } 
  
  return self;
}

// Here we create the master disease list. The list has 10 lines, corresponding
//   to the diseases, and 15 columns: the first one says if the disease is
//   active or not in the agent, the second gives the metabolism loss
//   caused by the disease and the third the number of bits. The 4th and 5th
//   columns are related to the treatment of the disease, and the 10 left
//   are the bits itself.
- genDiseaseList
{
  int i, j;
  
  for (i = 0; i < numDiseases; i++) {
   masterDiseaseList[i][0] = 0;
   masterDiseaseList[i][1] = [uniformIntRand getIntegerWithMin: 1 withMax: maxMetabLoss];
   masterDiseaseList[i][2] = [uniformIntRand getIntegerWithMin: 1 withMax: 10]; 
   masterDiseaseList[i][3] = 0;
   masterDiseaseList[i][4] = 0;
  }

  for (i = 0; i < numDiseases; i++) {
    for (j = 5; j < 5 + masterDiseaseList[i][2]; j++) {
      masterDiseaseList[i][j] = [uniformIntRand getIntegerWithMin: 0 withMax: 1];
    }
  }

  for (i = 0; i < numDiseases; i++) {
    for (j = 0; j < 5; j++)
      printf ("%d ", masterDiseaseList[i][j]);
    printf ("| ");
    for (j = 5; j < 5 + masterDiseaseList[i][2]; j++)
      printf ("%d ", masterDiseaseList[i][j]);
    printf ("\n");
  }

  return self;
}

@end
